抱歉,您的浏览器无法访问本站

本页面需要浏览器支持(启用)JavaScript


了解详情 >

Spring Cache 源码学习,自定义CacheManager

一、学习背景

  最近一直在学习Spring boot的框架,因为之前一直接触的都是Spring + Spring mvc + mybatis的ssm框架,对于约定大于配置的这种开发理念还是不太熟悉和上手,所以决定系统学习下Springboot。最近在看项目代码,对代码中Spring cache的一些自定义拓展很感兴趣,所以就好好整理下。(基于Spring 5.2.6-release 版本)

二、Spring Cache的源码学习

2.1、Spring Cache的核心接口

2.1.1、Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
package org.springframework.cache;

import java.util.concurrent.Callable;

import org.springframework.lang.Nullable;

/**
* 定义通用缓存操作的接口。
*
* <b>注意:</b>由于缓存的一般用法,建议实现允许存储<tt> null </tt>值(例如,存储返回{@code null}的方法)。
*
* @author Costin Leau
* @author Juergen Hoeller
* @author Stephane Nicoll
* @since 3.1
*/
public interface Cache {

/**
* 返回缓存的名称
*/
String getName();

/**
* 返回基础本机缓存提供程序。这里是指缓存实现类对象,返回类型为Class<T extend Cache> class
*/
Object getNativeCache();

/**
* 返回此缓存将指定键映射到的值。 <p>如果缓存不包含该键的映射,则返回{@code null}; 否则,缓存的值(本身可能为{@code null})将在{@link
* ValueWrapper}中返回。
* @param key 要返回其关联值的key
* @return 此缓存将指定键映射到的值,该值包含在{@link ValueWrapper}中,该值也可以保存缓存的{@code null}值。 直接返回{@code null}表示缓* 存不包含该键的映射。
* @see #get(Object, Class)
* @see #get(Object, Callable)
*/
@Nullable
ValueWrapper get(Object key);

/**
* 返回此缓存将指定键映射到的值,通常指定返回值将强制转换为的类型。 <p>注意:{@code get}的此变体不允许区分已缓存的{@code null}值和根本找不 * 到缓存条目。 为此,请使用标准的{@link #get(Object)}变体。
* @param key 要返回其关联值的key
* @param type 需要返回值的类型(可以为{@code null}来绕过类型检查;如果在缓存中找到{@code null}值,则指定的类型无关紧要)
* @return 此高速缓存将指定键映射到的值(本身可以是{@code null}),如果高速缓存不包含此键的映射,则还可以是{@code null}
* @throws IllegalStateException 如果找到了高速缓存条目但未能匹配指定的类型
* @since 4.0
* @see #get(Object)
*/
@Nullable
<T> T get(Object key, @Nullable Class<T> type);

/**
* 返回此缓存将指定键映射到的值,并在必要时从{@code valueLoader}获取该值。 此方法为常规的“如果已缓存,则返回;否则创建,缓存并返回”模式提供
* 了简单的替代方法。 <p>如果可能,实现应确保加载操作是同步的,以便在同时访问同一键的情况下,仅调用一次指定的{@code valueLoader}。 <p>如果
*{@code valueLoader}引发异常,则将其包装在{@link ValueRetrievalException}中
* @param key 要返回其关联值的key
* @return 此缓存将指定键映射到的值
* @throws ValueRetrievalException 如果{@code valueLoader}抛出异常
* @since 4.3
* @see #get(Object)
*/
@Nullable
<T> T get(Object key, Callable<T> valueLoader);

/**
* 将指定的值与此高速缓存中的指定键相关联。 <p>如果高速缓存先前包含此键的映射,则旧值将由指定值替换。 <p>可以以异步或延迟的方式执行实际注册,随
* 后的查找可能仍未看到该条目。 例如,事务性缓存装饰器可能就是这种情况。 使用{@link #putIfAbsent}保证立即注册。
* @param key the key with which the specified value is to be associated
* @param value the value to be associated with the specified key
* @see #putIfAbsent(Object, Object)
*/
void put(Object key, @Nullable Object value);

/**
* 如果尚未将指定值与该高速缓存中的指定键原子关联,则该值尚未设置。 <p>这等效于:<pre> <code> ValueWrapper existingValue = cache.get(key); 如果(existingValue == null){cache.put(key,value); 返回现存值; </ code> </ pre>,但该操作是原子执行的。 虽然所有开箱即用的{@link CacheManager}实现都可以自动执行放置,但操作也可以分两个步骤实现,例如 以非原子方式检查存在性并随后放置。 有关更多详细信息,请查看所使用的本机缓存实现的文档。 <p>默认实现沿着上面的代码片段委托给{@link #get(Object)}和{@link #put(Object,Object)}。
* @param key 与指定值关联的键
* @param value 与指定键关联的值
* @return 此缓存将指定键映射到的值(本身可能是{@code null}),如果在此调用之前缓存不包含该键的任何映射,则还为{@code null}。 因此,返回{@code null}表示给定的{@code value}已与密钥相关联。
* @since 4.1
* @see #put(Object, Object)
*/
@Nullable
default ValueWrapper putIfAbsent(Object key, @Nullable Object value) {
ValueWrapper existingValue = get(key);
if (existingValue == null) {
put(key, value);
}
return existingValue;
}

/**
* 如果存在,请从此缓存中退出此键的映射。 <p>可以以异步或延迟的方式执行实际逐出,随后的查找可能仍会看到该条目。 例如,事务性缓存装饰器可能就是这种情况。 使用{@link #evictIfPresent}确保立即删除。
* @param key 要从缓存中删除其映射的键
* @see #evictIfPresent(Object)
*/
void evict(Object key);

/**
* 如果存在此键,则从此缓存中退出该键的映射,并期望该键对于后续查找立即不可见。 <p>默认实现委派给{@link #evict(Object)},对于未确定的先前存在的键,返回{@code false}。 鼓励高速缓存提供者,尤其是高速缓存装饰器,如果可能的话(例如,在事务中通常延迟的高速缓存操作的情况下)执行立即驱逐并可靠地确定给定密钥的先前存在。
* @param key 要从缓存中删除其映射的键
* @return {@code true}(如果以前知道缓存对此键有映射),{@ code false}(如果没有)(或者无法确定先前的存在)
* @since 5.2
* @see #evict(Object)
*/
default boolean evictIfPresent(Object key) {
evict(key);
return false;
}

/**
* 通过删除所有映射来清除缓存。 <p>可以以异步或延迟的方式执行实际清除,随后的查找可能仍会看到条目。 例如,事务性缓存装饰器可能就是这种情况。 使用{@link #invalidate()}可以确保立即删除条目。
* @see #invalidate()
*/
void clear();

/**
* 通过删除所有映射来使缓存无效,并期望所有条目对于随后的查找立即不可见。 @return {@code true}(如果以前知道该缓存具有映射),{@ code false}(如果没有)(或者如果无法确定先前是否存在条目)
* @since 5.2
* @see #clear()
*/
default boolean invalidate() {
clear();
return false;
}


/**
* 代表缓存值的(包装)对象。
*/
@FunctionalInterface
interface ValueWrapper {

/**
* 返回缓存中的实际值。
*/
@Nullable
Object get();
}


/**
* 如果值加载程序回调因异常而失败,则从{@link #get(Object,Callable)}引发包装器异常。
* @since 4.3
*/
@SuppressWarnings("serial")
class ValueRetrievalException extends RuntimeException {

@Nullable
private final Object key;

public ValueRetrievalException(@Nullable Object key, Callable<?> loader, Throwable ex) {
super(String.format("Value for key '%s' could not be loaded using '%s'", key, loader), ex);
this.key = key;
}

@Nullable
public Object getKey() {
return this.key;
}
}

}

2.1.2、CacheManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*
* Copyright 2002-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cache;

import java.util.Collection;

import org.springframework.lang.Nullable;

/**
* Spring的中央缓存管理器SPI。
*
* <p>允许检索命名的{@link Cache}区域。
*
* @author Costin Leau
* @author Sam Brannen
* @since 3.1
*/
public interface CacheManager {

/**
* 获取与给定名称关联的缓存。 <p>请注意,如果本机提供程序支持,则缓存可能会在运行时延迟创建。
* @param name 缓存标识符(不得为{@code null})
* @return 关联的缓存,如果不存在或无法创建,则为{@code null}
*/
@Nullable
Cache getCache(String name);

/**
* 获取此管理器已知的缓存名称的集合。
* @return 缓存管理器已知的所有缓存的名称
*/
Collection<String> getCacheNames();

}

2.1.3、Cache相关的注解

Spring cache 为了方便我们对缓存的使用,提供了五个比较有用的注解:

@Cacheable
@CachePut
@CacheEvict
@Caching
@CacheConfig

2.1.3.1、@Cacheable

源码注释翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.concurrent.Callable;

import org.springframework.core.annotation.AliasFor;

/**
* 标注可以缓存调用方法(或类中的所有方法)结果的注释。
*
* <p>每次调用建议的方法时,都会应用缓存行为,检查是否已为给定参数调用了该方法。 通常默认设置只是使用方法参数来计算密钥,但是可以通过{@link #key}属性提供SpEL表达式,或者自定义{@link org.springframework.cache.interceptor.KeyGenerator}实现可以替换 默认值之一(请参见{@link #keyGenerator})。
*
* <p>如果在高速缓存中找不到所计算键的值,则将调用目标方法,并将返回的值存储在关联的高速缓存中。 请注意,Java8的{@code Optional}返回类型是自动处理的,并且其内容存储在缓存中(如果存在)。
*
* <p>此注释可用作<em>元注释</ em>,以创建具有属性覆盖的自定义<em>组成的注释</ em>。
*
* @author Costin Leau
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 3.1
* @see CacheConfig
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Cacheable {

/**
* 缓存名称的别名
*/
@AliasFor("cacheNames")
String[] value() default {};

/**
* 存储方法调用结果的缓存的名称。
* <p>名称可用于确定目标缓存(或多个缓存),与特定Bean定义的限定符值或Bean名称匹配。
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
*/
@AliasFor("value")
String[] cacheNames() default {};

/**
* 用于动态计算键的Spring表达式语言(SpEL)表达式。
*
* 默认值是{@code ""},意味着所有的方法参数都会被考虑作为一个key,除非自定义的key生成器生效。 {@link #keyGenerator}
*
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
*/
String key() default "";

/**
* 要使用的自定义{@link org.springframework.cache.interceptor.KeyGenerator}的bean名称。 <p>与{@link #key}属性互斥。
* @see CacheConfig#keyGenerator
*/
String keyGenerator() default "";

/**
* 自定义{@link org.springframework.cache.CacheManager}的bean名称,用于创建默认的{@link org.springframework.cache.interceptor.CacheResolver}(如果尚未设置)。
* <p>与{@link #cacheResolver}属性互斥。
* @see org.springframework.cache.interceptor.SimpleCacheResolver
* @see CacheConfig#cacheManager
*/
String cacheManager() default "";

/**
* 要使用的定制{@link org.springframework.cache.interceptor.CacheResolver}的bean名称。
* @see CacheConfig#cacheResolver
*/
String cacheResolver() default "";

/**
* Spring表达式语言(SpEL)表达式,用于使方法缓存成为条件。
* <p>默认值为{@code“”},这意味着方法结果始终被缓存。
* <p> SpEL表达式根据提供以下元数据的专用上下文进行评估:<ul>
* <li> {@ code#root.method},{@ code#root.target}和{@code #root。 缓存},分别引用{@link java.lang.reflect.Method方法},目标对象和受影响的缓存。</li>
* <li>方法名称的快捷方式({@code#root.methodName })和目标类别({@code#root.targetClass})也可用。
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args [1]},{@ code#p1}或{@code#a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li> </ ul>
*/
String condition() default "";

/**
* Spring表达式语言(SpEL)表达式用于否决方法缓存。
* <p>与{@link #condition}不同,此表达式是在调用方法后求值的,因此可以引用{@code result}。
* <p>默认值为{@code“”},表示永远不会否决缓存。
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
* @since 3.2
*/
String unless() default "";

/**
* 如果多个线程试图为同一键加载值,则同步基础方法的调用。 同步带来了两个限制:
* <ol>
* <li>不支持{@link #unless()}</li>
* <li>只能指定一个缓存</li>
* <li>不能合并其他与缓存相关的操作</li>
* </ol>
* 这实际上是一个提示,您正在使用的实际缓存提供程序可能不以同步方式支持它。 检查您的提供程序文档,以获取有关实际语义的更多详细信息。
* @since 4.3
* @see org.springframework.cache.Cache#get(Object, Callable)
*/
boolean sync() default false;

}

主要参数 描述 用法
value cacheNames 的别名 @Cacheable(“mysqlCache”)
cacheNames 缓存的名称 @Cacheable(cacheNames = MysqlCache.NAME)
key 缓存的key,支持Spring EL 表达式 @Cacheable(cacheNames = MysqlCache.NAME, key = “‘CacheDemoService-get’+ args[0]”) @Cacheable(cacheNames = MysqlCache.NAME, key = “‘CacheDemoService-get’+ #p0”)
@Cacheable(cacheNames = MysqlCache.NAME, key = “‘CacheDemoService-get’+ #a0”)
keyGenerator key的构造器,和key属性互斥 需要定义一个customerKeyGenerator的bean @Cacheable(cacheNames = MysqlCache.NAME, keyGenerator = “customerKeyGenerator”)
cacheManager 指定CacheManager,需要先定义CacheManager的bean,指定后则不能指定cacheResolver,两者互斥,如不指定,则使用注入的默认的CacheManagerBean @Cacheable(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”)
cacheResolver 指定cacheResolver的bean的名称,和cacheManager互斥 暂时未做测试
condition 是否使用方法缓存条件,默认“”,是否去取方法缓存,在方法执行之前计算条件结果 @Cacheable(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”,condition=“#a0 == ‘xx’”)
unless 否决方法的结果缓存,默认“”,始终缓存方法,如果条件不成立则不缓存方法结果,在方法执行结果后计算条件结果 @Cacheable(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”,unless=“#a0 == ‘xx’”)
sync 多线程访问方法,是否同步缓存,默认值为false,使用有三个限制,不支持unless,只能指定一个缓存,不能与@Caching一起使用 @Cacheable(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”,sync=false)
2.1.3.2、@CachePut

源码注释翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
* 指示方法(或类中的所有方法)触发{@link org.springframework.cache.Cache#put(Object,Object)cache put}操作的注释。
*
* <p>与{@link Cacheable @Cacheable}注释相反,此注释不会导致建议的方法被跳过。 而是,它总是使方法被调用并将其结果存储在关联的缓存中。 请注意,Java8的{@code Optional}返回类型是自动处理的,并且其内容存储在缓存中(如果存在)。
*
* <p>此注释可用作<em>元注释</ em>,以创建具有属性覆盖的自定义<em>组成的注释</ em>。
*
* @author Costin Leau
* @author Phillip Webb
* @author Stephane Nicoll
* @author Sam Brannen
* @since 3.1
* @see CacheConfig
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CachePut {

/**
* 别名{@link #cacheNames}.
*/
@AliasFor("cacheNames")
String[] value() default {};

/**
* 用于缓存放置操作的缓存名称。
* <p>名称可以用来确定目标缓存(或多个缓存),与特定bean定义的限定符值或bean名称匹配。
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
*/
@AliasFor("value")
String[] cacheNames() default {};

/**
* 用于动态计算键的Spring表达式语言(SpEL)表达式。
*
* 默认值是{@code ""},意味着所有的方法参数都会被考虑作为一个key,除非自定义的key生成器生效。 {@link #keyGenerator}
*
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
*/
String key() default "";

/**
* 要使用的自定义{@link org.springframework.cache.interceptor.KeyGenerator}的bean名称。 <p>与{@link #key}属性互斥。
* @see CacheConfig#keyGenerator
*/
String keyGenerator() default "";

/**
* 自定义{@link org.springframework.cache.CacheManager}的bean名称,用于创建默认的{@link org.springframework.cache.interceptor.CacheResolver}(如果尚未设置)。
* <p>与{@link #cacheResolver}属性互斥。
* @see org.springframework.cache.interceptor.SimpleCacheResolver
* @see CacheConfig#cacheManager
*/
String cacheManager() default "";

/**
* 要使用的定制{@link org.springframework.cache.interceptor.CacheResolver}的bean名称。
* @see CacheConfig#cacheResolver
*/
String cacheResolver() default "";

/**
* 用于动态计算键的Spring表达式语言(SpEL)表达式。
*
* 默认值是{@code ""},意味着所有的方法参数都会被考虑作为一个key,除非自定义的key生成器生效。 {@link #keyGenerator}
*
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
*/
String condition() default "";

/**
* 用于动态计算键的Spring表达式语言(SpEL)表达式。
*
* 默认值是{@code ""},意味着所有的方法参数都会被考虑作为一个key,除非自定义的key生成器生效。 {@link #keyGenerator}
*
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
*/
String unless() default "";

}

主要参数 描述 注释
value 同Cacheable @CachePut(“mysqlCache”)
cacheNames 同Cacheable @CachePut(cacheNames=”mysqlCache”)
key 同Cacheable @CachePut(cacheNames=”mysqlCache”,key=”#a1”)
keyGenerator 同Cacheable @CachePut(cacheNames=”mysqlCache”,keyGenerator=”customKeyGenerator”)
cacheManager 同Cacheable @CachePut(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”)
cacheResolver() 同Cacheable 暂未测试
condition 同Cacheable @CachePut(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”,condition=”#a1 == ‘xxx’”)
unless 同Cacheable @CachePut(cacheNames = MysqlCache.NAME, cacheManager=”mysqlCacheManager”,keyGenerator = “customerKeyGenerator”,unless=”#a1 == ‘xxx’”)
2.1.3.3、@CacheEvict

源码注释翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

import org.springframework.core.annotation.AliasFor;

/**
* 表示方法(或类中的所有方法)触发{@link org.springframework.cache.Cache#evict(Object)cache evict}操作的注释。
*
* <p>此注释可用作<em>元注释</ em>,以创建具有属性覆盖的自定义<em>组成的注释</ em>。
*
* @author Costin Leau
* @author Stephane Nicoll
* @author Sam Brannen
* @since 3.1
* @see CacheConfig
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface CacheEvict {

/**
* 别名 {@link #cacheNames}.
*/
@AliasFor("cacheNames")
String[] value() default {};

/**
* 用于缓存逐出操作的缓存名称。 <p>名称可用于确定目标缓存(或多个缓存),与特定Bean定义的限定符值或Bean名称匹配。
* @since 4.2
* @see #value
* @see CacheConfig#cacheNames
*/
@AliasFor("value")
String[] cacheNames() default {};

/**
* 用于动态计算键的Spring表达式语言(SpEL)表达式。
*
* 默认值是{@code ""},意味着所有的方法参数都会被考虑作为一个key,除非自定义的key生成器生效。 {@link #keyGenerator}
*
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
*/
String key() default "";

/**
* 自定义key生成器的bean名字{@link org.springframework.cache.interceptor.KeyGenerator}
* to use.
* <p>与 {@link #key} 互斥
* @see CacheConfig#keyGenerator
*/
String keyGenerator() default "";

/**
* 自定义{@link org.springframework.cache.CacheManager}的bean名称,用于创建默认的{@link org.springframework.cache.interceptor.CacheResolver}(如果尚未设置)。
* <p>与{@link #cacheResolver} 互斥.
* @see org.springframework.cache.interceptor.SimpleCacheResolver
* @see CacheConfig#cacheManager
*/
String cacheManager() default "";

/**
* 自定义的bean的名称{@link org.springframework.cache.interceptor.CacheResolver}
* to use.
* @see CacheConfig#cacheResolver
*/
String cacheResolver() default "";

/**
* 用于动态计算键的Spring表达式语言(SpEL)表达式。
*
* 默认值是{@code ""},意味着所有的方法参数都会被考虑作为一个key,除非自定义的key生成器生效。 {@link #keyGenerator}
*
* spring el表达式的计算取决于上小文提供的元数据:
* 包含{@code #root.method}, {@code #root.target}, and {@code #root.caches} 引用{@link java.lang.reflect.Method method},目标对象,和受影响的缓存。还提供了({@code #root.methodName}) 方法名称 ({@code #root.targetClass})目标类名称的快捷方式
* 方法参数可以作为关联索引
* <li>方法参数可以通过索引访问。 例如,可以通过{@code#root.args[1]},{@ code #p1}或{@code #a1}访问第二个自变量。 如果该信息可用,也可以按名称访问参数。</li>
*/
String condition() default "";

/**
* 是否删除缓存内的所有条目。
* <p>默认情况下,仅删除关联键下的值。
* <p>请注意,不允许将此参数设置为{@code true}并指定{@link #key}。
*/
boolean allEntries() default false;

/**
* 在调用该方法之前是否应该收回。
* <p>将此属性设置为{@code true},将导致驱逐发生,而与方法结果无关(即,是否引发异常)。
* <p>默认值为{@code false},这意味着将在成功调用建议的方法后(即仅在调用未引发异常的情况下)</ em>进行缓存逐出操作。
*/
boolean beforeInvocation() default false;

}

主要参数 描述 用法
value 同Cacheable @CacheEvict(“mysqlCache”)
cacheNames 同Cacheable @CacheEvict(cacheNames=”mysqlCache”)
key 同Cacheable @CacheEvict(cacheNames=”mysqlCache”,key=”#a1”)
keyGenerator 同Cacheable @CacheEvict(cacheNames=”mysqlCache”,key=”customKeyGenerator”)
cacheManager 同Cacheable @CacheEvict(cacheNames=”mysqlCache”,key=”customKeyGenerator”,cacheManager=”customCacheManager”)
cacheResolver 同Cacheable 暂时未测试
condition 同Cacheable @CacheEvict(cacheNames=”mysqlCache”,key=”#a1”,condition=”#a1 == true”)
unless 同Cacheable @CacheEvict(cacheNames=”mysqlCache”,key=”#a1”,unless=”#a1 == true”)
allEntries 是否删除该缓存实例下的所有缓存的信息,默认值:false,当值为true时,则与属性key互斥 @CacheEvict(cacheNames=”mysqlCache”,allEntries = false
beforeInvocation 在调用该方法之前是否移除缓存,默认为false,如果为true时,则移除缓存和方法是否执行异常无关 @CacheEvict(cacheNames=”mysqlCache”,key=”#a1”,unless=”#a1 == true”,beforeInvocation = true)
2.1.3.4、@Caching

源码注释翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* 多个(不同或相同类型的)缓存注释的组注释。
*
* <p>此注释可用作<em>元注释</ em>,以创建具有属性覆盖的自定义<em>组成的注释</ em>。
*
* @author Costin Leau
* @author Chris Beams
* @since 3.1
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Caching {

Cacheable[] cacheable() default {};

CachePut[] put() default {};

CacheEvict[] evict() default {};

}

2.1.3.5、@CacheConfig

源码注释翻译:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package org.springframework.cache.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* {@code @CacheConfig}提供了一种在类级别共享与缓存相关的常见设置的机制。
*
* <p>当给定类上存在此批注时,它将为该类中定义的任何缓存操作提供一组默认设置。
*
* @author Stephane Nicoll
* @author Sam Brannen
* @since 4.1
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {

/**
* 要为带注释的类中定义的缓存操作考虑的默认缓存的名称。
* <p>如果在操作级别未设置任何值,则将使用这些值而不是默认值。
* <p>可用于确定目标缓存(或多个缓存),与
* 限定符值或特定bean定义的bean名称。
*/
String[] cacheNames() default {};

/**
* 默认{@link org.springframework.cache.interceptor.KeyGenerator}的bean名
* <p>如果在操作级别未设置任何值,则使用该值代替默认值。
* <p>密钥生成器与自定义密钥互斥。 当这样的钥匙是
* 为操作定义的,此密钥生成器的值将被忽略。
*/
String keyGenerator() default "";

/**
* 自定义{@link org.springframework.cache.CacheManager}的bean名称,用于创建默认{@link org.springframework.cache.interceptor.CacheResolver}(如果尚未设置)。
* <p>如果在操作级别未设置解析器和缓存管理器,并且未通过{@link #cacheResolver}设置缓存解析器,则使用该解析器而不是默认值。 @see org.springframework.cache.interceptor.SimpleCacheResolver
*/
String cacheManager() default "";

/**
* 要使用的定制{@link org.springframework.cache.interceptor.CacheResolver}的bean名称。
* <p>如果在操作级别未设置解析器和缓存管理器,则使用该解析器而不是默认值。
*/
String cacheResolver() default "";

}

该注解只能使用在类上,为该类中的@Cacheable、@CachePut、@CacheEvict提供cacheNames、cacheManager、keyGenerator、cacheResolver默认的设置。不难发现这些值都是Spring 的bean的实例名称。

主要参数 描述 用法
cacheNames 缓存名称的bean的名称,默认值:“”,则以实际的设置为准 @CacheConfig(cacheNames=”mysqlCache”)
cacheManager 缓存管理器的bean的名称,默认值:“”,则以实际的设置为准 @CacheConfig(cacheManager=”customCacheManager”)
keyGenerator key生成器的bean的名称,默认值:“”,则以实际的设置为准 @CacheConfig(keyGenerator=”customKeyGnenrator”)
cacheResolver 缓存实现的bean的名称,默认值:“”,则以实际的设置为准 @CacheConfig(cacheResolver=”customCacheResolver”)

三、Spring Cache的实现原理

3.1、基本实现原理

  Spring的缓存实现,依赖于Spring AOP,通过获取上面讲述的五个注解的切点位置,来实现对应的功能。
  那么意味着如果要使用缓存或者缓存生效前提,所有的缓存方法调用实例都必须是Spring 代理的Bean。否则的话就无法使用。

3.2、核心实现类

Spring Cache的核心类的依赖关系图:

cache aspect
cache opration

Spring Cache的执行逻辑:

Spring Cache的执行逻辑

3.3、Spring Cache 注解失效的场景

在开发的过程中,可能会发现缓存没有生效,原因可能如下:

1、方法所在的Bean没有注入到Spring的容器
2、方法是私有的或者是final修饰的
3、方法的调用对象为非Spring的代理类,类似于this.method()方式调用

四、自定义Spring Cache

在学习的过程中,做了一个简单的小demo拓展了一个Cache方式,尝试将缓存存储到Mysql的数据表中。

4.1、准备阶段

技术选型 版本
java 1.8.0_241
Spring boot 2.3.0.release
Spring jpa 2.3.0.release
数据库 Mysql 5.x
Spring boot test 2.3.0.release
实体类加强工具 lombok

4.2、创建缓存表的CRUD

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
package com.yiyi.cache.cache.entity;

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;

/**
* @author wuxuan.chai
* @date 2020/6/4 1:42 下午
*/
@Data
@Entity(name = "t_cache")
public class CacheObject {

@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

@Column(name = "cache_key",nullable = false)
private String cacheKey;

@Column(name = "cache_value",nullable = false)
private String cacheValue;

@Column(name = "create_date",nullable = false)
private LocalDateTime createDate;


}


package com.yiyi.cache.cache.entity;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.transaction.annotation.Transactional;

/**
* @author wuxuan.chai
* @date 2020/6/4 4:07 下午
*/
public interface CacheObjectMapper extends JpaRepository<CacheObject,String> {

CacheObject getByCacheKey(String cacheKey);

@Modifying
@Transactional
void deleteCacheObjectByCacheKey(String cacheKey);
}


package com.yiyi.cache.cache.entity;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.SneakyThrows;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;

/**
* @author wuxuan.chai
* @date 2020/6/4 4:08 下午
*/
@Service
public class CacheObjectService {

@Resource
private CacheObjectMapper cacheObjectMapper;


public String getValue(String key){
CacheObject cacheObject = cacheObjectMapper.getByCacheKey(key);
if (cacheObject == null){
return null;
}
return cacheObject.getCacheValue();
}

@SneakyThrows
@Transactional(rollbackFor = Exception.class)
public void putValue(String key, String value) {
CacheObject oldCacheObject = cacheObjectMapper.getByCacheKey(key);
if (oldCacheObject != null){
cacheObjectMapper.delete(oldCacheObject);
}

CacheObject cacheObject = new CacheObject();
cacheObject.setCacheKey(key);
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(value);
cacheObject.setCacheValue(json);
cacheObject.setCreateDate(LocalDateTime.now());
cacheObjectMapper.save(cacheObject);
}

public void evict(String key) {
cacheObjectMapper.deleteCacheObjectByCacheKey(key);
}

@Transactional
public void clear() {
cacheObjectMapper.deleteAll();
}
}


4.3、Spring Cache的拓展

4.3.1、定义CacheManager

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.yiyi.cache.cache.manager;

import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;

import java.util.Collection;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* @author wuxuan.chai
* @date 2020/6/4 3:52 下午
*/
public class CustomCacheManager implements CacheManager {

//定义存储Cache的存储器,所有拓展并且实例化的Cache对象都会存到这里面
private final static ConcurrentMap<String,Cache> cacheMap = new ConcurrentHashMap<>();

//通过名称,获取Cache实例化对象,我们定义Cacheable的注解用到的CacheNames属性的值,所标注的名称获取Cache实例就是这个方法提供的
@Override
public Cache getCache(String name) {
return cacheMap.get(name);
}

//获取所有的cache实例化对象的名称
@Override
public Collection<String> getCacheNames() {
return cacheMap.keySet();
}

//创建Cache实例
public void createCache(Cache cache){
cacheMap.put(cache.getName(),cache);
}

}

4.3.2、定义Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
package com.yiyi.cache.cache.manager;


import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.yiyi.cache.cache.entity.CacheObjectService;
import com.yiyi.cache.cache.serializer.CustomSerializerImpl;
import lombok.SneakyThrows;
import org.springframework.beans.BeansException;
import org.springframework.cache.Cache;
import org.springframework.cache.support.SimpleValueWrapper;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;

import java.util.concurrent.Callable;

/**
* @author wuxuan.chai
* @date 2020/6/4 4:02 下午
*/
@Component
public class MysqlCache implements Cache, ApplicationContextAware {
public final static String NAME = "MYSQL";

private ApplicationContext applicationContext;


@Override
public String getName() {
return NAME;
}

@Override
public Object getNativeCache() {
return this;
}

@Override
public ValueWrapper get(Object key) {
String value = applicationContext.getBean(CacheObjectService.class).getValue(key.toString());
return value != null ? new SimpleValueWrapper(fromStoreValue(value)) : null;
}

@SneakyThrows
@Override
public <T> T get(Object key, Class<T> type) {
String value = applicationContext.getBean(CacheObjectService.class).getValue(key.toString());
return (T) fromStoreValue(value);
}

@SneakyThrows
@Override
public <T> T get(Object key, Callable<T> valueLoader) {
String value = applicationContext.getBean(CacheObjectService.class).getValue(key.toString());
return (T) fromStoreValue(value);
}

@Override
public void put(Object key, Object value) {
String cacheValue = toStoreValue(value);
applicationContext.getBean(CacheObjectService.class).putValue(key.toString(), cacheValue);
}

@Override
public void evict(Object key) {
applicationContext.getBean(CacheObjectService.class).evict(key.toString());
}

@Override
public void clear() {
applicationContext.getBean(CacheObjectService.class).clear();
}

@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}

@SneakyThrows
private String toStoreValue(Object value){
CustomSerializerImpl customSerializer = new CustomSerializerImpl();
return customSerializer.serialize(value);
}

@SneakyThrows
private Object fromStoreValue(String value){
CustomSerializerImpl customSerializer = new CustomSerializerImpl();
return customSerializer.deserialize(value);
}
}

4.3.3、配置Cache

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.yiyi.cache.cache.config;

import com.yiyi.cache.cache.manager.CustomCacheManager;
import com.yiyi.cache.cache.manager.MysqlCache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.concurrent.ConcurrentMapCache;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.annotation.Resource;

/**
* @author wuxuan.chai
* @date 2020/6/4 3:40 下午
*/
@Configuration
public class CacheConfig {

@Resource
private MysqlCache mysqlCache;

//定义了两个Cache:一个自己实现的MysqlCache(Mysql) 还有一个就是Spring Cache默认实现了的基于并发包ConcurrentMap实现的的ConcurrentMapCache (LOCAL)
@Bean
public CacheManager cacheManager(){
CustomCacheManager customCacheManager = new CustomCacheManager();
customCacheManager.createCache(mysqlCache);
ConcurrentMapCache local = new ConcurrentMapCache("LOCAL");
customCacheManager.createCache(local);
return customCacheManager;
}
}

4.4.4、Cache的使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
package com.yiyi.cache.cache.service;

import com.yiyi.cache.cache.entity.User;
import com.yiyi.cache.cache.manager.MysqlCache;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;

/**
* @author wuxuan.chai
* @date 2020/6/4 2:46 下午
*/
@Service
public class CacheDemoService {

@Cacheable(cacheNames = MysqlCache.NAME, key = "'CacheDemoService-get'+ args[0]")
public String get(String name) {
return name;
}

@Cacheable(cacheNames = MysqlCache.NAME, keyGenerator = "customKeyGenerator")
public User keyGenerator(String name, String value) {
User user = new User();
user.setUsername(name+value);
return user;
}

@Cacheable(cacheNames = MysqlCache.NAME, key = "'CacheDemoService-getUser'+ args[0]")
public User getUser(String name) {
User user = new User();
user.setUsername(name);
return user;
}

@Cacheable(cacheNames = "LOCAL", key = "'CacheDemoService-get'+ args[0]")
public String getLocalCache(String name) {
return name;
}

@Caching(evict = {
@CacheEvict(cacheNames = {MysqlCache.NAME}, key = "'CacheDemoService-get'+ args[0]"),
@CacheEvict(cacheNames = MysqlCache.NAME, key = "'CacheDemoService-getUser'+ args[0]")
})
public void clearCache(String name) {

}


}

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import com.yiyi.cache.SpringCacheApplication;
import com.yiyi.cache.cache.entity.User;
import com.yiyi.cache.cache.service.CacheDemoService;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;

/**
* @author wuxuan.chai
* @date 2020/6/4 2:49 下午
*/
@SpringBootTest(classes = {SpringCacheApplication.class})
public class CacheDemoServiceTest {
@Resource
private CacheDemoService cacheDemoService;

@Test
public void testGet(){
String name = "1231231";
String s = cacheDemoService.get(name);
System.out.println(s);

System.out.println(cacheDemoService.get("1231").equals("1231"));
}

@Test
public void testGetUser(){
String name = "1231231";
User user = cacheDemoService.getUser(name);
System.out.println(user.getUsername().equals(name));
}

@Test
public void testGetLocalCache(){
String name = "1231231";
String s = cacheDemoService.getLocalCache(name);
System.out.println(s);

System.out.println(cacheDemoService.getLocalCache("1231").equals("1231"));
}

@Test
public void testClearCache(){
cacheDemoService.clearCache("1231231");
}

@Test
public void testKeyGenerator(){
User wuxuan = cacheDemoService.keyGenerator("wuxuan", "123456");
System.out.println(wuxuan);
}
}

五、总结

  通过阅读Spring Cache的源码后,感觉收获到了很多,首先从心理上认识到,源码也并没有那么的难。其次,Spring 能够受到广大Java开发人员的追捧也并非偶然,Spring的源码从思路上以及设计上还是非常的优秀的,从中能够学到很多。以后再接再厉,多多尝试Discover Why 而不 Ask Why。

评论